/*
 * Copyright 2013 Robert Peszek.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fpig.monad.functions

import fpig.applicative.functions.BaseA;
import fpig.concepts.ApplicativeDescription;
import fpig.concepts.MonadAsApplicative;
import fpig.concepts.MonadDescription;
import fpig.lists.functions.Base;
import fpig.lists.structures.FunList;
import fpig.util.CallUtil;
import fpig.common.functions.FpigBase
import static fpig.expressions.IfElseSyntax.*
import static fpig.groovylists.functions.InAndOutOfFunLists.*
import static fpig.lists.functions.Base.*

/**
 * Defines base (standard) monadic functions
 * @author robert peszek
 *
 * @param <F>
 * @param <T>
 */
class BaseM<A,M>  {
   static <A,M> BaseM<A,M> getInstance(MonadDescription<A, M> monad){
	   ApplicativeDescription appF = new MonadAsApplicative(monad)
	   new BaseM(monad: monad, appFunctor: appF)	   
   }
      
   MonadDescription<A, M> monad
   ApplicativeDescription appFunctor
   
  /**
   * TODO document how it is done
   * Logical signature
   * (a->b) -> m a-> m b
   */
  Closure getLift() {
	  //c :: (a->b) -> m a -> m b} 
	  appFunctor.fmap
  }
  
  /**
   * Logical signature
   * (a->b-> m a) -> a -> [b] -> m a
   * @return
   */
  Closure getFoldLM() {
	  Closure foldL = Base.foldL
	  CallUtil.toFunction { Closure foldF, acc, l-> 
		  Closure foldFM = {ma, b -> 
			  monad.bind(foldF(CallUtil._,b), ma) 
		  }
		  foldL(foldFM, monad.pure(acc), l)
	  }
  }
  
  /**
   * Logical signature
   * (m a->boolean) -> (a->b-> m a) -> a -> [b] -> m a
   * @return
   */
  Closure getFoldLMUntil() {
	  Closure foldLUntil = Base.foldLUntil
	  CallUtil.toFunction { Closure precicateF, Closure foldF, acc, l->
		  Closure foldFM = {ma, b ->
			  monad.bind(foldF(CallUtil._,b), ma)
		  }
		  foldLUntil(precicateF, foldFM, monad.pure(acc), l)
	  }
  }
  
  /**
   * Logical signature
   * m (m a) -> m a
   * @return
   */
  Closure getJoin() {
	  
	  return monad.bind(FpigBase.ID)
  }
  
  /**
   * Applicative function
   * Logical signature 
   * m(a->b) -> m a-> m b
   * 
   */
  Closure getAp() {	  
	  appFunctor.ap
  }
  /**
   * Convenience 3 arg version of app.
   * For using applicative with more arguments, please use folds.
   * 
   * logical signature:
   * m(a->b->c) -> m a-> m b-> m c
   * @return
   */
  Closure getAp2() {
	  BaseA app = BaseA.getInstance(appFunctor)
      app.ap2
  }
  
  Closure getApN() {
	  BaseA app = BaseA.getInstance(appFunctor)
      app.apN
  }
  
  /**
   * Both meanings of unpure are correct: it 'reverses' the {@code pure} function (generalizing functions like unright or unjust) and is intended to invoke side-effects.
   * <p>
   * This function result will simply replay passed monadic value, but will also invoke passed closure.
   * Because it replays the monadic value it can be easily inserted into monadic composition.
   * FpigBase also defines flipped version named {@code asUnpure}
   * <p>
   * Example:
   * <pre>
   * Closure diet = { person ->
   *           if(person.weight>150)
   *              right([name:person.name, weight: person.weight-20])
   *           else
   *              left('eat more, you are too skinny')
   *       }
   *
   * def jon = [name: 'Jon', weight: 160]
   * def jonAfter1 = diet(jon)
   *
   * unpure(jonAfter1) { //alternative to unright
   *   assert it.weight == 140
   * }
   *
   * unpure(_, {println 'should not print that'}) << b(diet) << unpure(_,{println "jon is now ${it.weight} lb"}) << b(diet) << unpure(_,{println "jon is now ${it.weight} lb"}) << right(jon)
   * 
   * asUnpure {println 'should not print that'} << b(diet) << asUnpure {println "jon is now ${it.weight} lb"} << b(diet) << asUnpure {println "jon is now ${it.weight} lb"} << right(jon)
   * </pre>
   * <p>
   * logical signature:
   * m a -> (a->_) -> m a
   */
  Closure getUnpure() {
	  CallUtil.toFunction { M mx, Closure sideEffectC->
		 monad.bind ({x ->
		   sideEffectC(x)
		   monad.pure(x)
		 }, mx) 
	  }
  }
}

